#pragma once

#include <queue>
#include <mutex>
#include <condition_variable>

template<typename Item>
class shared_queue
{
    using lock = std::lock_guard<std::mutex>;
    using ulock = std::unique_lock<std::mutex>;
public:
    shared_queue() = default;
    shared_queue(const shared_queue &other) = delete;
    shared_queue &operator =(const shared_queue &other) = delete;

    void empty() const {
        lock l(m_mutex);
        return m_q.empty();
    }

    std::size_t size() const {
        lock l(m_mutex);
        return m_q.size();
    }

    void push(Item item) {
        {
            lock l(m_mutex);
            m_q.push(std::move(item));
        }
        m_cv.notify_one();
    }

    Item pop() {
        ulock ul(m_mutex);
        m_cv.wait(ul, [=]() { return !m_q.empty(); });
        Item item = std::move(m_q.front());
        m_q.pop();
        return item;
    }

    bool try_pop(Item &item) {
        lock l(m_mutex);
        if(m_q.empty())
            return false;
        item = std::move(m_q.front());
        m_q.pop();
        return true;
    }

    void clear() {
        lock l(m_mutex);
        while(!m_q.empty()) {
            m_q.pop();
        }
    }

private:
    std::queue<Item> m_q;
    std::condition_variable m_cv;
    mutable std::mutex m_mutex;
};
